package cn.summit.springGlobalHandler;

import cn.summit.constants.CommonConstants;
import cn.summit.exception.ArgsInvalidException;
import cn.summit.exception.BusinessException;
import cn.summit.exception.SystemException;
import cn.summit.response.Result;
import cn.summit.validation.dto.ValidError;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * controller异常处理 日志打印
 *
 * @author cn.summit
 * @since 2020/4/19 22:08
 */
@RestControllerAdvice
@Slf4j
public class GlobalBaseController implements ResponseBodyAdvice<Object> {

    @Value("${spring.profiles.active:}")
    private String env;

    /**
     * 用于处理通用异常
     */
    @ExceptionHandler(Throwable.class)
    public Result<?> errorHandler(Throwable e, HttpServletResponse response) {
        log.error("系统错误", e);
        String data = null;
        if (!"prod".equals(env)) {
            //非生产环境,输出异常具体原因,方便调试
            data = e.getMessage();
        }
        return Result.failed(CommonConstants.FAILED_CODE, "服务器出错了，请联系管理员", data);
    }

    /**
     * 参数检查异常 用于处理通用异常 捕获
     *
     * @param e @validate 未加@RequestBody
     * @return 错误信息
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> bindException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder errorMessage = new StringBuilder("参数检查异常:");

        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMessage.append("字段[").append(fieldError.getField()).append("],入参值:")
                .append(fieldError.getRejectedValue()).append(",原因: ")
                .append(fieldError.getDefaultMessage()).append(";");
        }
        log.error(errorMessage.toString());
        return Result.failed(errorMessage.toString());
    }

    /**
     * 参数校验失败
     *
     * @param e 参数异常
     * @return 参数校验结果
     */
    @ResponseBody
    @ExceptionHandler(value = ArgsInvalidException.class)
    public Result<List<ValidError>> errorHandler(ArgsInvalidException e) {
        return Result.failed(CommonConstants.FAILED_INVALID_CODE, e.getMessage(), e.getErrors());
    }

    /**
     * 参数校验失败异常处理
     *
     * @param e MethodArgumentNotValidException
     * @return Result
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<List<ValidError>> errorHandler(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        List<ValidError> errors = new ArrayList<>();
        for (ObjectError error : allErrors) {
            FieldError err = (FieldError) error;
            errors.add(new ValidError(err.getField(),
                err.getDefaultMessage() + "[当前值:" + ((FieldError) error).getRejectedValue() + "]"));
        }
        return Result.failed(CommonConstants.FAILED_INVALID_CODE, "参数错误", errors);
    }

    /**
     * 用于处理通用异常
     * <p>
     * 捕获 @RequestBody @Valid List<UserDTO> list 和 getTestVal(@PathVariable @TextLength(max = 5)
     * String val)
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> bindException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        StringBuilder errorMesssage = new StringBuilder("校验失败:");
        for (ConstraintViolation<?> constraintViolation : constraintViolations) {
            errorMesssage.append(constraintViolation.getMessage());
            log.error(constraintViolation.getMessage());
        }
        return Result.failed(errorMesssage.toString());
    }

    /**
     * 业务异常处理
     *
     * @param e 异常
     * @return 业务异常
     */
    @ResponseBody
    @ExceptionHandler(value = BusinessException.class)
    public Result<?> errorHandler(BusinessException e) {
        log.info("业务异常", e);
        return Result.failed(e.getStatusCode(), e.getMessage(), e.getData());
    }

    /**
     * 系统异常处理
     *
     * @param e 系统异常
     * @return 系统异常
     */
    @ResponseBody
    @ExceptionHandler(value = SystemException.class)
    public Result<?> errorHandler(SystemException e) {
        log.error("系统异常（SystemException）- {}", e.getMessage(), e);
        Object data = e.getData();
        if (!"prod".equals(env) && data == null) {
            //非生产环境,输出异常具体原因,方便调试
            data = e.getMessage();
        }
        return Result.failed(e.getStatusCode(), "服务器出错了，请联系管理员", data);
    }

    @Override
    public boolean supports(MethodParameter methodParameter,
        @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        Class<?> clazz = methodParameter.getDeclaringClass();
        ResponseBody responseBody = AnnotatedElementUtils
            .findMergedAnnotation(clazz, ResponseBody.class);
        return responseBody != null;
    }


    @Override
    public Object beforeBodyWrite(@Nullable Object body, @NonNull MethodParameter methodParameter,
        @Nullable MediaType mediaType, @Nullable Class aClass,
        @Nullable ServerHttpRequest serverHttpRequest,
        @Nullable ServerHttpResponse serverHttpResponse) {

        //  Class<?> declaringClass = methodParameter.getDeclaringClass();
        if (body == null || !Result.class.isAssignableFrom(body.getClass())) {
            return Result.success(body);
        }
        return body;
    }


}
